package org.micode.common.utils;

import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class ObjectUtils {

    public static <T> T createByClassName(String className, Object... args)
            throws NoSuchMethodException, ClassNotFoundException,
            IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<?> c = Class.forName(className);

        List<Class<?>> parameterTypes = null;
        if (args.length > 0) {
            parameterTypes = new ArrayList<>();
            for (Object arg : args) {
                parameterTypes.add(arg.getClass());
            }
        }

        Constructor<?> constructor;
        if (null == parameterTypes) {
            constructor = c.getConstructor();
        } else {
            Class<?>[] arr = new Class<?>[parameterTypes.size()];
            parameterTypes.toArray(arr);
            constructor = c.getConstructor(arr);
        }

        return (T) constructor.newInstance(args);
    }

    public static void copyProperties(Map<String, String> sourceMap, Object target) throws BeansException {
        Assert.notNull(sourceMap, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        final Method[] allMethodList = target.getClass().getMethods();

        sourceMap.forEach((key, value) -> {
            String methodName = "set" + key.substring(0, 1).toUpperCase() + key.substring(1);
            try {
                Method setMethod = readTheMethod(allMethodList, methodName);
                if (null != setMethod && setMethod.getParameterCount() == 1) {
                    Class<?>[] paramTypes = setMethod.getParameterTypes();
                    Class<?> paramType = paramTypes[0];
                    if (StringExtUtils.isAnyEmpty(value)) {
                        setMethod.invoke(target, (Object) null);
                    } else if (paramType.equals(Integer.class)) {
                        setMethod.invoke(target, Integer.valueOf(value));
                    } else if (paramType.equals(Long.class)) {
                        setMethod.invoke(target, Long.valueOf(value));
                    } else if (paramType.equals(String.class)) {
                        setMethod.invoke(target, value);
                    } else if (paramType.equals(Double.class)) {
                        setMethod.invoke(target, Double.valueOf(value));
                    } else if (paramType.equals(LocalDateTime.class)) {
                        setMethod.invoke(target, DateUtils.str2LocalDateTime(value));
                    } else if (paramType.equals(LocalDate.class)) {
                        setMethod.invoke(target, DateUtils.str2LocalDate(value));
                    } else if (paramType.equals(LocalTime.class)) {
                        setMethod.invoke(target, LocalTime.parse(value));
                    }
                }
            } catch (IllegalAccessException |
                    InvocationTargetException e) {
                e.printStackTrace();
            }
        });
    }

    private static Method readTheMethod(Method[] allMethodList, String methodName) {
        for (Method method : allMethodList) {
            if (methodName.equals(method.getName())) {
                return method;
            }
        }
        return null;
    }

    public static void copyProperties(Object source, Object target) throws BeansException {
        copyPropertiesNotNull(source, target, (Class) null, (String[]) null);
    }

    private static void copyPropertiesNotNull(Object source, Object target,
                                              Class<?> editable,
                                              String... ignoreProperties)
            throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName()
                        + "] not assignable to Editable class [" + editable.getName() + "]");
            }

            actualEditable = editable;
        }

        PropertyDescriptor[] targetPds = BeanExtUtils.getPropertyDescriptors(actualEditable);
        List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
        PropertyDescriptor[] var7 = targetPds;
        int var8 = targetPds.length;

        for (int var9 = 0; var9 < var8; ++var9) {
            PropertyDescriptor targetPd = var7[var9];
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = BeanExtUtils.getPropertyDescriptor(source.getClass(),
                        targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],
                            readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }

                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            if (value != null) {
                                writeMethod.invoke(target, value);
                            }
                        } catch (Throwable var15) {
                            throw new FatalBeanException("Could not copy property '" + targetPd.getName()
                                    + "' from source to target", var15);
                        }
                    }
                }
            }
        }

    }

}
